package cn.sunjinxin.trade.application.service;

import cn.hutool.core.lang.Assert;
import cn.sunjinxin.savior.core.common.Result;
import cn.sunjinxin.savior.core.helper.ThreadLocalHelper;
import cn.sunjinxin.savior.event.context.EventContext;
import cn.sunjinxin.savior.event.control.Eventer;
import cn.sunjinxin.savior.lock.Locker;
import cn.sunjinxin.savior.lock.locker.Lock;
import cn.sunjinxin.trade.application.convert.PreorderConvert;
import cn.sunjinxin.trade.application.work.ValidateWorkContainer;
import cn.sunjinxin.trade.application.work.pipeline.ProductValidateWorkPipeline;
import cn.sunjinxin.trade.client.api.TradeService;
import cn.sunjinxin.trade.client.api.request.*;
import cn.sunjinxin.trade.client.api.response.*;
import cn.sunjinxin.trade.domain.enums.TocType;
import cn.sunjinxin.trade.domain.even.TradeEvent;
import cn.sunjinxin.trade.domain.gateway.OrderGateway;
import cn.sunjinxin.trade.domain.model.*;
import cn.sunjinxin.trade.domain.service.OrderDomainService;
import cn.sunjinxin.trade.domain.service.PayDomainService;
import cn.sunjinxin.trade.domain.service.ProductDomainService;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;

import javax.annotation.Resource;

/**
 * 144种交易场景
 * 扩展点视图
 * 状态机轮转追溯
 *
 * @author issavior
 */
@DubboService
@Slf4j
public class TradeServiceImpl implements TradeService {

    @Resource
    private ProductDomainService productDomainService;
    @Resource
    private OrderDomainService orderDomainService;
    @Resource
    private PayDomainService payDomainService;
    @Resource
    private OrderGateway orderGateway;

    @Override
    @SentinelResource(value = "trade_order_validate")
    public Result<OrderDTO> orderValidate(OrderRequest request) {
        // 1. 分布式锁
        Lock lock = Locker.tryLock("trade_order_validate" + request.getDistributorId() + request.getOutOrderNo(),
                () -> new RuntimeException("预下单频繁"));

        try {
            // 1. 获取spu
            ProductSpu productSpu = productDomainService.getProductSpuWithSplitOrder(request.getProductSpuId(), request.getBuyQuantity());

            // 2. 业务校验
            new ProductValidateWorkPipeline().addLast(ValidateWorkContainer.PREORDER.list()).validate(productSpu, request);

            // 3. 预下单（系统商占单｜预占库存）
            productDomainService.preOrder(productSpu.getProductSkus());

            // 4. 持久化中台订单
            Order b2bOrder = orderDomainService.batchSaveOrder(productSpu, PreorderConvert.getDistributor(request), PreorderConvert.getTravelers(request));

            // 5. 发布预下单成功事件,同时注册收单超时事件
            Toc toc = Toc.init(b2bOrder, TocType.CREATE_ORDER_TIME_OUT);
            toc.register();
            Eventer.SYNC.publish(EventContext.<TradeEvent, Order>builder()
                    .eventId(String.valueOf(b2bOrder.getId()))
                    .eventType(TradeEvent.PREORDER)
                    .request(b2bOrder)
                    .build());
            return Result.ok(OrderDTO.builder().orderId(b2bOrder.getId()).build());
        } catch (Exception exception) {
            log.error("预下单失败:{}", exception.getMessage());
            return Result.failed("预下单失败");
        } finally {
            lock.unlock();
        }
    }

    @Override
    @SentinelResource(value = "trade_order_create")
    public Result<OrderCreateDTO> orderCreate(OrderRequest request) {
        // 1. 分布式锁
        Lock lock = Locker.tryLock("trade_order_create" + request.getDistributorId() + request.getOutOrderNo(), () -> new RuntimeException("下单频繁"));

        try {
            Order b2bOrder = orderDomainService.getB2bOrder(request.getDistributorId(), request.getOutOrderNo());
            Assert.isTrue(b2bOrder != null && b2bOrder.isInit(), "订单不存在");

            // 1. 获取spu
            ProductSpu productSpu = productDomainService.getProductSpuWithSplitOrder(request.getProductSpuId(), request.getBuyQuantity());

            // 2. 业务校验
            new ProductValidateWorkPipeline().addLast(ValidateWorkContainer.PREORDER.list()).validate(productSpu, request);

            // 5. 推单
            orderDomainService.pushOrder(TradeEvent.CREATE, b2bOrder);

            // 6. 发布创单成功事件
            Eventer.ASYNC.publish(EventContext.<TradeEvent, Order>builder()
                    .eventId(String.valueOf(b2bOrder.getId()))
                    .eventType(TradeEvent.CREATE)
                    .request(b2bOrder)
                    .build());
        } catch (Exception exception) {
            log.error("创单失败:{}", exception.getMessage());
            return Result.failed("创单失败");
        } finally {
            lock.unlock();
        }

        return Result.ok();
    }

    @Override
    @SentinelResource(value = "trade_order_pay")
    public Result<String> pay(OrderPayRequest request) {

        // 1. 分布式锁
        String key = request.getUserId() + request.getOrderNo();
        Lock lock = Locker.tryLock("trade_order_pay" + key, () -> new RuntimeException("支付频繁"));

        try {
            Order order = orderGateway.getOrderById(request.getOrderNo());
            ThreadLocalHelper.put("order", order);
            // 2. 验单
//            new PreOrderValidateWorkPipeline().addLast(PreorderValidateWorkContainer.ORDER_PAY_URL.list()).validate(request);

            // 3. 获取支付url
            String tradeUrl = payDomainService.pay(null);

            FundOrder fundOrder = payDomainService.createFundOrder(order, request.getUserId());
            Eventer.ASYNC.publish(EventContext.<TradeEvent, Order>builder()
                    .eventId(String.valueOf(order.getId()))
                    .eventType(TradeEvent.PAY)
                    .request(order)
                    .build());
            return Result.ok(tradeUrl);
        } catch (Exception exception) {
            log.error("支付失败:{}", exception.getMessage());
            return Result.failed("支付失败");
        } finally {
            ThreadLocalHelper.close();
            lock.unlock();
        }
    }

    @Override
    public Result<String> paySuccess(OrderPaySuccessRequest orderPaySuccessRequest) {
        return null;
    }
}